﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NinjaToolbox.SubComponentModule;
using NinjaToolbox.PhysicsSystem;

namespace NinjaToolbox.BattleSystem
{
    [DisallowMultipleSubComponent]
    public class BattleObject : MonoBehaviour, ISerializationCallbackReceiver
    {
        [Serializable]
        public class CollisionSettings
        {
            public PhysicsObject physicsObject;
            public bool enableTriggerStayEvent;
            public bool enableTriggerExitEvent;
            public bool enableSelfTriggerEvent = true;
        }

        BattleInfoContext mBattleInfoContext;

        [SerializeField]
        ComponentSerializeUnion[] mSubComponentContainerSerializeDataArray;

        public BattleObject_FactionConstInfo factionConstInfo;
        public int faction;
        public FactionComparer factionComparer;
        public BattleObject_AttachCollider[] attachColliders;
        public CollisionSettings collisionSettings = new CollisionSettings();
        SubComponentContainer mBattleSubComponentContainer = new SubComponentContainer();

        public SubComponentContainer BattleSubComponentContainer { get { return mBattleSubComponentContainer; } }

        public event Action<BattleInfoContext> OnContactOtherFactionCallback;

        public event Action<BattleInfoContext> OnBattleTriggerEnterCallback;
        public event Action<BattleInfoContext> OnBattleTriggerStayCallback;
        public event Action<BattleInfoContext> OnBattleTriggerExitCallback;


        protected virtual void onBattleObject_Collider_TriggerEnterCallback(BattleObject_AttachCollider sender, PhysicsObject other)
        {
            var otherBattleObject = other.GetComponent<BattleObject>();

            if (otherBattleObject != null)
            {
                if (factionComparer.Compare(faction, otherBattleObject.faction) == FactionComparer.HOSTILE_FACTION)
                {
                    mBattleInfoContext = new BattleInfoContext() { ContactBattleObject = otherBattleObject, AttachCollider = sender };

                    OnContactOtherFactionCallback?.Invoke(mBattleInfoContext);
                }

                OnBattleTriggerEnterCallback?.Invoke(mBattleInfoContext);
            }
        }

        protected virtual void onBattleObject_Collider_TriggerStayCallback(BattleObject_AttachCollider sender, PhysicsObject other)
        {
            if (!collisionSettings.enableTriggerStayEvent) return;

            var otherBattleObject = other.GetComponent<BattleObject>();

            if (otherBattleObject != null)
            {
                if (factionComparer.Compare(faction, otherBattleObject.faction) == FactionComparer.HOSTILE_FACTION)
                {
                    mBattleInfoContext = new BattleInfoContext() { ContactBattleObject = this, AttachCollider = sender };

                    OnBattleTriggerStayCallback?.Invoke(mBattleInfoContext);
                }
            }
        }

        protected virtual void onBattleObject_Collider_TriggerExitCallback(BattleObject_AttachCollider sender, PhysicsObject other)
        {
            if (!collisionSettings.enableTriggerExitEvent) return;

            var otherBattleObject = other.GetComponent<BattleObject>();

            if (otherBattleObject != null)
            {
                if (factionComparer.Compare(faction, otherBattleObject.faction) == FactionComparer.HOSTILE_FACTION)
                {
                    mBattleInfoContext = new BattleInfoContext() { ContactBattleObject = this, AttachCollider = sender };

                    OnBattleTriggerExitCallback?.Invoke(mBattleInfoContext);
                }
            }
        }

        protected virtual void Awake()
        {
            mBattleSubComponentContainer.DoAwake();

            mBattleSubComponentContainer.Initialization(
                new SubComponentContext() { Container = mBattleSubComponentContainer, Owner = this }
                , (enumerator) => StartCoroutine(enumerator));

            collisionSettings.physicsObject.OnTriggerEnter += onPhysicsObjectTriggerEnterCallback;
            collisionSettings.physicsObject.OnTriggerStay += onPhysicsObjectTriggerStayCallback;
            collisionSettings.physicsObject.OnTriggerExit += onPhysicsObjectTriggerExitCallback;
        }

        protected virtual void Start()
        {
            mBattleSubComponentContainer.DoStart();
        }

        protected virtual void Update()
        {
            mBattleSubComponentContainer.DoUpdate();
        }

        protected virtual void LateUpdate()
        {
            mBattleSubComponentContainer.DoLateUpdate();
        }

        protected virtual void FixedUpdate()
        {
            mBattleSubComponentContainer.DoFixedUpdate();
        }

        protected virtual void OnEnable()
        {
            for (int i = 0, iMax = attachColliders.Length; i < iMax; i++)
            {
                var collider = attachColliders[i];

                collider.BindToBattleObject(this);

                attachColliders[i].OnTriggerEnterCallback += onBattleObject_Collider_TriggerEnterCallback;
                attachColliders[i].OnTriggerStayCallback += onBattleObject_Collider_TriggerStayCallback;
                attachColliders[i].OnTriggerExitCallback += onBattleObject_Collider_TriggerExitCallback;
            }

            var battleObjectSubComponents = mBattleSubComponentContainer.GetSubComponents<BattleObject_SubComponentBase>();
            for (int i = 0, iMax = battleObjectSubComponents.Length; i < iMax; i++)
            {
                var battleObjectSubComponent = battleObjectSubComponents[i];
                battleObjectSubComponent.Initialization(this);
            }

            mBattleSubComponentContainer.DoOnEnable();
        }

        protected virtual void OnDisable()
        {
            for (int i = 0, iMax = attachColliders.Length; i < iMax; i++)
            {
                var collider = attachColliders[i];

                collider.OnTriggerEnterCallback -= onBattleObject_Collider_TriggerEnterCallback;
                collider.OnTriggerStayCallback -= onBattleObject_Collider_TriggerStayCallback;
                collider.OnTriggerExitCallback -= onBattleObject_Collider_TriggerExitCallback;
            }

            mBattleSubComponentContainer.DoOnDisable();
        }

        protected virtual void OnDestroy()
        {
            mBattleSubComponentContainer.DoOnDestroy();

            collisionSettings.physicsObject.OnTriggerEnter -= onPhysicsObjectTriggerEnterCallback;
            collisionSettings.physicsObject.OnTriggerStay -= onPhysicsObjectTriggerStayCallback;
            collisionSettings.physicsObject.OnTriggerExit -= onPhysicsObjectTriggerExitCallback;
        }

        protected virtual void OnBeforeSerialize()
        {
            mSubComponentContainerSerializeDataArray = mBattleSubComponentContainer.SerializeToUnion();
        }

        protected virtual void OnAfterDeserialize()
        {
            if (mSubComponentContainerSerializeDataArray != null)
            {
                mBattleSubComponentContainer.DeserializeFromUnion(mSubComponentContainerSerializeDataArray);
                mSubComponentContainerSerializeDataArray = null;
            }
        }

        void onPhysicsObjectTriggerEnterCallback(PhysicsObject self, PhysicsObject other)
        {
            if (!collisionSettings.enableSelfTriggerEvent) return;

            onBattleObject_Collider_TriggerEnterCallback(null, other);
        }

        void onPhysicsObjectTriggerStayCallback(PhysicsObject self, PhysicsObject other)
        {
            if (!collisionSettings.enableSelfTriggerEvent) return;

            onBattleObject_Collider_TriggerStayCallback(null, other);
        }

        void onPhysicsObjectTriggerExitCallback(PhysicsObject self, PhysicsObject other)
        {
            if (!collisionSettings.enableSelfTriggerEvent) return;

            onBattleObject_Collider_TriggerExitCallback(null, other);
        }

        #region --- ISerializationCallbackReceiver Members ---

        void ISerializationCallbackReceiver.OnBeforeSerialize()
        {
            OnBeforeSerialize();
        }

        void ISerializationCallbackReceiver.OnAfterDeserialize()
        {
            OnAfterDeserialize();
        }
        #endregion
    }
}
